/* Copyright 2009 Requirements Management System 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.mags.remas.server;

import java.util.Dictionary;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.eclipse.core.runtime.Platform;
import org.eclipse.equinox.http.jetty.JettyConfigurator;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleException;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;

/**
 * 
 * @author Mario Gaitan
 *
 */
public class EmbeddedServer {
	private String host;
	private int port = -1;
	private static final int AUTO_SELECT_JETTY_PORT = 0;
	private static final String START_SERVER_ACTION = "START";
	private static final String STOP_SERVER_ACTION = "STOP";
	private static final String STOP_ALL_SERVER_ACTION = "STOP_ALL";
	private static EmbeddedServer instance = null;
	private Map<String, WorkerThread> webApps = new HashMap<String, WorkerThread>();
	public static final String DEFAULT_WEBAPP_NAME = "remas";
	
	public static EmbeddedServer getInstance() {
		if(instance==null)
			instance = new EmbeddedServer();
		return instance;
	}
	
	private abstract class WorkerThread extends Thread {	
		private Throwable exception;
		public WorkerThread(String name) {
			super(name);
		}
		public synchronized void setException(Throwable status) {
			this.exception = status;
		}	
		public synchronized Throwable getException() {
			return exception;
		}
	}
	private final class StartServerThread extends WorkerThread {
		private final String webappName;
		public StartServerThread(String webappName) {
			super("Start Help Server");
			this.webappName = webappName;
		}
		@SuppressWarnings("unchecked")
		public void run() {
			try {
				@SuppressWarnings("rawtypes")
				final Dictionary d = new Hashtable();
				configurePort();
				d.put("http.port", new Integer(getPortParameter()));
				d.put("context.path", "/remas"); 
				d.put("other.info", "org.mags.remas");
				Logger.getLogger("org.mortbay").setLevel(Level.WARNING);	
				JettyConfigurator.startServer(webappName, d);
			} catch (Throwable t) {
				setException(t);
			}
		}
	}
	private final class StopServerThread extends WorkerThread {
		private final String webappName;
		public StopServerThread(String webappName) {
			super("Stop Help Server");
			this.webappName = webappName;
		}
		public void run() {
			try {
				JettyConfigurator.stopServer(webappName);
			} catch (Throwable t) {
				setException(t); 
			}
		}
	}
	public void start()  throws Exception {
		start(DEFAULT_WEBAPP_NAME);
	}
	private void stopServers() throws Exception {
		Exception exception = null;
		for(Object _webappName: webApps.keySet().toArray()) {
			String webAppName = (String)_webappName;
			try {
				stopServer(webAppName);
			} catch(Exception e) {
				exception = e;
			}
		}
		if(exception!=null)
			throw exception;
	}
	private void stopServer(String webappName)  throws Exception {
		if(webApps.get(webappName)!=null) {
			WorkerThread stopRunnable = new StopServerThread(webappName);
			execute(stopRunnable);
			webApps.remove(webappName);
		}
	}
	private void startServer(String webappName) throws Exception {
		if(webApps.get(webappName)==null) {
			WorkerThread startRunnable = new StartServerThread(webappName); 
			execute(startRunnable);		
			checkBundle();
			webApps.put(webappName, startRunnable);
		} 
	}
	private synchronized void execute(String action, Object... params) throws Exception {
		if(START_SERVER_ACTION.equalsIgnoreCase(action))
			startServer((String)params[0]);
		else if(STOP_SERVER_ACTION.equalsIgnoreCase(action))
			stopServer((String)params[0]);
		else if(STOP_ALL_SERVER_ACTION.equalsIgnoreCase(action))
			stopServers();
	}
	public void start(final String webappName) throws Exception {
		execute(START_SERVER_ACTION, webappName);
	}
	public void stop(final String webappName) throws Exception {
		execute(STOP_SERVER_ACTION, webappName);
	}	
	public void stopAll() throws Exception  {
		execute(STOP_ALL_SERVER_ACTION);
	}
	private void checkBundle() throws InvalidSyntaxException, BundleException {
		Bundle bundle = Platform.getBundle("org.eclipse.equinox.http.registry"); //$NON-NLS-1$if (bundle != null) {
		if (bundle.getState() == Bundle.RESOLVED) {
			bundle.start(Bundle.START_TRANSIENT);
		}
		if (port == -1) {
			ServiceReference[] reference = bundle.getBundleContext().getServiceReferences("org.osgi.service.http.HttpService", "(other.info=org.mags.remas)"); //$NON-NLS-1$ //$NON-NLS-2$
			Object assignedPort = reference[0].getProperty("http.port"); //$NON-NLS-1$
			port = Integer.parseInt((String)assignedPort);
		}
	}
	public void stop() throws Exception {
		stop(DEFAULT_WEBAPP_NAME);
	}
	private void execute(WorkerThread runnable) throws Exception {
		boolean interrupted = false;
		Thread thread = runnable;
		thread.setDaemon(true);
		thread.start();
		while(true) {
			try {
				thread.join();
				break;
			} catch (InterruptedException e) {
				interrupted = true;
			}
		}
		if (interrupted)
			Thread.currentThread().interrupt();
		Throwable t = runnable.getException();
		if (t != null) {
			if (t instanceof Exception) {
				throw (Exception)t;
			}
			throw (Error) t;
		}
	}
	public int getPort() {
		return port;
	}
	private void configurePort() {
		if (port == -1) {
			String portCommandLineOverride = ""; //TODO: crear una forma de obtener el puerto definido por el usuario
			if (portCommandLineOverride != null && portCommandLineOverride.trim().length() > 0) {
				try {
					port = Integer.parseInt(portCommandLineOverride);
				}
				catch (NumberFormatException e) {
					throw new RuntimeException(e);
				}
			}
		}
	}
	private int getPortParameter() {
		if (port == -1) { 
			return AUTO_SELECT_JETTY_PORT;
		}
		return port;
	}
	public String getHost() {
		if (host == null) {
			String hostCommandLineOverride = ""; //TODO: crear una forma para obtener este valor definido por el usuario 
			if (hostCommandLineOverride != null && hostCommandLineOverride.trim().length() > 0) {
				host = hostCommandLineOverride;
			}
			else {
				host = "127.0.0.1"; 
			}
		}
		return host;
	}	
}
